//The MIT License
//
//Copyright (c) 2009 nodchip
//
//Permission is hereby granted, free of charge, to any person obtaining a copy
//of this software and associated documentation files (the "Software"), to deal
//in the Software without restriction, including without limitation the rights
//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//copies of the Software, and to permit persons to whom the Software is
//furnished to do so, subject to the following conditions:
//
//The above copyright notice and this permission notice shall be included in
//all copies or substantial portions of the Software.
//
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//THE SOFTWARE.
package tv.dyndns.kishibe.server;

import java.util.List;
import java.util.Map;
import java.util.Set;

import tv.dyndns.kishibe.client.packet.PacketBbsResponse;
import tv.dyndns.kishibe.client.packet.PacketBbsThread;
import tv.dyndns.kishibe.client.packet.PacketChatData;
import tv.dyndns.kishibe.client.packet.PacketLinkData;
import tv.dyndns.kishibe.client.packet.PacketProblemCreationLog;
import tv.dyndns.kishibe.client.packet.PacketProblemData;
import tv.dyndns.kishibe.client.packet.PacketProblemDataMinimum;
import tv.dyndns.kishibe.client.packet.PacketRankingData;
import tv.dyndns.kishibe.client.packet.PacketUserData;
import tv.dyndns.kishibe.client.packet.PacketWrongAnswer;
import tv.dyndns.kishibe.server.ProblemManager.PacketProblemDataMinimums;
import tv.dyndns.kishibe.server.relevance.SentencesProcessor;
import tv.dyndns.kishibe.server.util.Pair;

import com.google.common.collect.MapMaker;

public class CachedDatabase implements Database {
	private final Database database = new DirectDatabase();

	@Override
	public void addChatLog(PacketChatData data) {
		database.addChatLog(data);
	}

	@Override
	public void addPlayerAnswers(int problemID, int type, List<String> answers) {
		database.addPlayerAnswers(problemID, type, answers);
	}

	@Override
	public int addProblemData(PacketProblemData data) {
		return database.addProblemData(data);
	}

	@Override
	public void addProblemIdsToReport(int userCode, int[] problemIds) {
		database.addProblemIdsToReport(userCode, problemIds);
	}

	@Override
	public void addRatingHistory(int userCode, int rating) {
		database.addRatingHistory(userCode, rating);
	}

	@Override
	public void addServerIgnoreUserCode(int userCode) {
		database.addServerIgnoreUserCode(userCode);
	}

	@Override
	public void clearProblemFeedback(int problemId) {
		database.clearProblemFeedback(problemId);
	}

	@Override
	public void clearProblemIdFromReport(int userCode) {
		database.clearProblemIdFromReport(userCode);
	}

	@Override
	public PacketProblemData[] getAdsenseProblems(String query) {
		return database.getAdsenseProblems(query);
	}

	@Override
	public List<PacketProblemDataMinimum> getMinimumProblemDatas() {
		return database.getMinimumProblemDatas();
	}

	@Override
	public PacketProblemDataMinimums getMinimumProblemDatasForThemeMode(String[] queries) {
		return database.getMinimumProblemDatasForThemeMode(queries);
	}

	@Override
	public PacketWrongAnswer[] getPlayerAnswers(int problemID) {
		return database.getPlayerAnswers(problemID);
	}

	@Override
	public PacketProblemData[] getProblemData(List<Integer> ids) {
		return database.getProblemData(ids);
	}

	@Override
	public List<String> getProblemFeedback(int problemId) {
		return database.getProblemFeedback(problemId);
	}

	@Override
	public Map<Integer, List<Integer>> getRatingGroupedByPrefecture() {
		return database.getRatingGroupedByPrefecture();
	}

	@Override
	public int[] getRatingHistory(int userCode) {
		return database.getRatingHistory(userCode);
	}

	@Override
	public PacketProblemData[] getUserProblemReport(int userCode) {
		return database.getUserProblemReport(userCode);
	}

	@Override
	public int[] getWholeRating() {
		return database.getWholeRating();
	}

	@Override
	public boolean isUsedUserCode(int userCode) {
		return database.isUsedUserCode(userCode);
	}

	@Override
	public PageView loadPageView() {
		return database.loadPageView();
	}

	@Override
	public void processProblems(SentencesProcessor sentencesProcessor) {
		database.processProblems(sentencesProcessor);
	}

	@Override
	public void removePlayerAnswers(int problemID) {
		database.removePlayerAnswers(problemID);
	}

	@Override
	public void removeProblemIdFromReport(int userCode, int problemID) {
		database.removeProblemIdFromReport(userCode, problemID);
	}

	@Override
	public void savePageView(PageView pageView) {
		database.savePageView(pageView);
	}

	@Override
	public PacketProblemData[] searchProblem(String query, String creater, int genre, int type, int randomFlag) {
		return database.searchProblem(query, creater, genre, type, randomFlag);
	}

	@Override
	public PacketProblemData[] searchProblemDataByCreater(String creater) {
		return database.searchProblemDataByCreater(creater);
	}

	@Override
	public PacketProblemData[] searchSimilarProblemData(String query) {
		return database.searchSimilarProblemData(query);
	}

	@Override
	public void updateMinimumProblemData(PacketProblemDataMinimum data) {
		database.updateMinimumProblemData(data);
	}

	@Override
	public void updateProblemData(PacketProblemData data) {
		database.updateProblemData(data);
	}

	@Override
	public void updateThemeModeScore(int userCode, String theme, int score) {
		database.updateThemeModeScore(userCode, theme, score);
	}

	@Override
	public void voteToProblem(int problemId, boolean good, String feedback) {
		database.voteToProblem(problemId, good, feedback);
	}

	// //////////////////////////////////////////////////////////////////////////////
	// キャッシュ
	public CachedDatabase() {
		ThreadPool.getInstance().addDailyTask(new Runnable() {
			public void run() {
			}
		});

		ThreadPool.getInstance().addHourTask(new Runnable() {
			public void run() {
				updateNumberOfActiveUsers();
				updateRankingData();
				updateThemeModeQuery();
				updateThemeModeRaking();
				updateLastestProblems();

				userDatas.clear();
				creationHistories.clear();
				bbsThreadCache.clear();
				bbsResponseCache.clear();
				linkDataCache.clear();
			}
		});

		ThreadPool.getInstance().addMinuteTasks(new Runnable() {
			public void run() {
				updateIgnoreServerUserCode();
				updateLimitedUser();
			}
		});
	}

	// //////////////////////////////////////////////////////////////////////////////
	// ユーザーデータ
	private final Map<Integer, PacketUserData> userDatas = new MapMaker().softKeys().softValues().makeMap();

	public PacketUserData getUserData(int userCode) {
		synchronized (userDatas) {
			PacketUserData userData = userDatas.get(userCode);
			if (userData == null) {
				userDatas.put(userCode, userData = database.getUserData(userCode));
			}
			return userData;
		}
	}

	public void setUserData(PacketUserData data) {
		synchronized (userDatas) {
			userDatas.put(data.userCode, data);
			database.setUserData(data);
		}
	}

	public void removeIgnoreUserCode(int userCode, int targetUserCode) {
		synchronized (userDatas) {
			userDatas.remove(userCode);
			database.removeIgnoreUserCode(userCode, targetUserCode);
		}
	}

	public void addIgnoreUserCode(int userCode, int targetUserCode) {
		synchronized (userDatas) {
			userDatas.remove(userCode);
			database.addIgnoreUserCode(userCode, targetUserCode);
		}
	}

	// //////////////////////////////////////////////////////////////////////////////
	// ランキング
	private volatile PacketRankingData[][] rankingDatas = database.getRankingData();

	public PacketRankingData[][] getRankingData() {
		return rankingDatas;
	}

	private void updateRankingData() {
		rankingDatas = database.getRankingData();
	}

	// //////////////////////////////////////////////////////////////////////////////
	// サーバー全体に適用される無視コード
	private volatile Set<Integer> serverIgnoreUserCode = database.getServerIgnoreUserCode();

	public Set<Integer> getServerIgnoreUserCode() {
		return serverIgnoreUserCode;
	}

	private void updateIgnoreServerUserCode() {
		serverIgnoreUserCode = database.getServerIgnoreUserCode();
	}

	// //////////////////////////////////////////////////////////////////////////////
	// 制限ユーザーコード
	private volatile Set<Integer> limitedUserCodes = database.getLimitedUserCodes();
	private volatile Set<String> limitedIps = database.getLimitedIps();

	public Set<Integer> getLimitedUserCodes() {
		return limitedUserCodes;
	}

	public Set<String> getLimitedIps() {
		return limitedIps;
	}

	private void updateLimitedUser() {
		limitedUserCodes = database.getLimitedUserCodes();
		limitedIps = database.getLimitedIps();
	}

	// //////////////////////////////////////////////////////////////////////////////
	// 問題変更履歴
	private final Map<Integer, PacketProblemCreationLog[]> creationHistories = new MapMaker().softKeys().softValues().makeMap();

	public PacketProblemCreationLog[] getProblemCreationHistory(int problemId) {
		synchronized (creationHistories) {
			PacketProblemCreationLog[] logs = creationHistories.get(problemId);
			if (logs == null) {
				creationHistories.put(problemId, logs = database.getProblemCreationHistory(problemId));
			}
			return logs;
		}
	}

	public void addCreationLog(int problemId, int userCode, String machineIP) {
		synchronized (creationHistories) {
			creationHistories.remove(problemId);
			database.addCreationLog(problemId, userCode, machineIP);
		}
	}

	// //////////////////////////////////////////////////////////////////////////////
	// 掲示板
	private final Map<Pair<Integer, Integer>, PacketBbsThread[]> bbsThreadCache = new MapMaker().softKeys().softValues().makeMap();
	private final Map<Pair<Integer, Integer>, PacketBbsResponse[]> bbsResponseCache = new MapMaker().softKeys().softValues().makeMap();
	private volatile int numberOfBbsThreads = Integer.MIN_VALUE;
	private final Object bbsLockObject = new Object();

	public PacketBbsThread[] getBbsThread(int start, int count) {
		synchronized (bbsLockObject) {
			final Pair<Integer, Integer> key = new Pair<Integer, Integer>(start, count);
			PacketBbsThread[] threads = bbsThreadCache.get(key);
			if (threads == null) {
				bbsThreadCache.put(key, threads = database.getBbsThread(start, count));
			}
			return threads;
		}
	}

	public PacketBbsResponse[] getBbsResponses(int threadId, int count) {
		synchronized (bbsLockObject) {
			final Pair<Integer, Integer> key = new Pair<Integer, Integer>(threadId, count);
			PacketBbsResponse[] result = bbsResponseCache.get(key);
			if (result == null) {
				bbsResponseCache.put(key, result = database.getBbsResponses(threadId, count));
			}
			return result;
		}
	}

	public void buildBbsThread(PacketBbsThread thread, PacketBbsResponse response) {
		synchronized (bbsLockObject) {
			database.buildBbsThread(thread, response);
			bbsThreadCache.clear();
			bbsResponseCache.clear();
			numberOfBbsThreads = Integer.MIN_VALUE;
		}
	}

	public void writeToBbs(PacketBbsResponse response, boolean age) {
		synchronized (bbsLockObject) {
			database.writeToBbs(response, age);
			bbsResponseCache.clear();
			bbsThreadCache.clear();
			numberOfBbsThreads = Integer.MIN_VALUE;
		}
	}

	public int getNumberOfBbsThread() {
		synchronized (bbsLockObject) {
			if (numberOfBbsThreads < 0) {
				numberOfBbsThreads = database.getNumberOfBbsThread();
			}
			return numberOfBbsThreads;
		}
	}

	// //////////////////////////////////////////////////////////////////////////////
	// リンク
	private final Map<Pair<Integer, Integer>, PacketLinkData[]> linkDataCache = new MapMaker().softKeys().softValues().makeMap();
	private volatile int numberOfLinkData = Integer.MIN_VALUE;
	private final Object linkDataLockObject = new Object();

	public void addLinkData(PacketLinkData linkData) {
		synchronized (linkDataLockObject) {
			database.addLinkData(linkData);
			linkDataCache.clear();
			numberOfLinkData = Integer.MIN_VALUE;
		}
	}

	public void updateLinkData(PacketLinkData linkData) {
		synchronized (linkDataLockObject) {
			database.updateLinkData(linkData);
			linkDataCache.clear();
		}
	}

	public void removeLinkData(int id) {
		synchronized (linkDataLockObject) {
			database.removeLinkData(id);
			linkDataCache.clear();
			numberOfLinkData = Integer.MIN_VALUE;
		}
	}

	public PacketLinkData[] getLinkDatas(int start, int count) {
		synchronized (linkDataLockObject) {
			final Pair<Integer, Integer> key = new Pair<Integer, Integer>(start, count);
			PacketLinkData[] result = linkDataCache.get(key);
			if (result == null) {
				linkDataCache.put(key, result = database.getLinkDatas(start, count));
			}
			return result;
		}
	}

	public int getNumberOfLinkDatas() {
		synchronized (linkDataLockObject) {
			if (numberOfLinkData < 0) {
				numberOfLinkData = database.getNumberOfLinkDatas();
			}
			return numberOfLinkData;
		}
	}

	// //////////////////////////////////////////////////////////////////////////////
	// アクティブユーザ数
	private volatile int numberOfActiveUsers = database.getNumberOfActiveUsers();

	public int getNumberOfActiveUsers() {
		return numberOfActiveUsers;
	}

	private void updateNumberOfActiveUsers() {
		numberOfActiveUsers = database.getNumberOfActiveUsers();
	}

	// //////////////////////////////////////////////////////////////////////////////
	// テーマモード
	private volatile Map<String, List<String>> themeModeQuery = database.getThemeModeQuery();
	private final Object lockThemeModeQuery = new Object();

	public Map<String, List<String>> getThemeModeQuery() {
		synchronized (lockThemeModeQuery) {
			return themeModeQuery;
		}
	}

	private void updateThemeModeQuery() {
		synchronized (lockThemeModeQuery) {
			themeModeQuery = database.getThemeModeQuery();
		}
	}

	@Override
	public void addThemeModeQuery(String theme, String query) {
		synchronized (lockThemeModeQuery) {
			database.addThemeModeQuery(theme, query);
			updateThemeModeQuery();
		}
	}

	@Override
	public void removeThemeModeQuery(String theme, String query) {
		synchronized (lockThemeModeQuery) {
			database.removeThemeModeQuery(theme, query);
			updateThemeModeQuery();
		}
	}

	// テーマモードランキング
	private volatile Map<String, PacketRankingData[]> themeModeRanking = database.getThemeModeRankingData();

	public Map<String, PacketRankingData[]> getThemeModeRankingData() {
		return themeModeRanking;
	}

	private void updateThemeModeRaking() {
		themeModeRanking = database.getThemeModeRankingData();
	}

	// //////////////////////////////////////////////////////////////////////////////
	// 最新の投稿問題
	private volatile PacketProblemData[] lastestProblems = database.getLastestProblems();

	@Override
	public PacketProblemData[] getLastestProblems() {
		return lastestProblems;
	}

	private void updateLastestProblems() {
		lastestProblems = database.getLastestProblems();
	}

	// //////////////////////////////////////////////////////////////////////////////
	// テーマモード編集者
	private volatile Map<Integer, Integer> themeModeEditors = database.getThemeModeEditorsStatus();

	@Override
	public Map<Integer, Integer> getThemeModeEditorsStatus() {
		return themeModeEditors;
	}

	@Override
	public void updateThemeModeEdtorsStatus(int userCode, int status) {
		database.updateThemeModeEdtorsStatus(userCode, status);
		themeModeEditors = database.getThemeModeEditorsStatus();
	}
}
